package com.acoreful.acf2java.admin.modules.auth.service.impl;

import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.acoreful.acf2java.admin.modules.auth.model.AuthUser;
import com.acoreful.acf2java.admin.modules.auth.service.AuthService;
import com.acoreful.acf2java.admin.modules.auth.service.AuthUserService;
import com.acoreful.acf2java.admin.modules.auth.support.CurrentStaff;
import com.acoreful.acf2java.admin.modules.auth.support.security.Encryption;
import com.acoreful.acf2java.admin.modules.auth.support.security.PasswordEncoder;
import com.acoreful.acf2java.commons.redis.RedisKeys;
import com.acoreful.acf2java.commons.redis.RedisOps;
import com.acoreful.acf2java.commons.utils.JsonUtils;

@Service
public class AuthServiceImpl implements AuthService {

	@Autowired
	private RedisOps redisOps;
	@Autowired
	private Encryption encryption;
	@Autowired
	private AuthUserService authUserService;
	private static final long SESSION_INVALIDATE_IN_MINUTES = 120L; // 默认session失效时间120分钟
	private long sessionInvalidateInMinutes = SESSION_INVALIDATE_IN_MINUTES;

	public void setSessionInvalidateInMinutes(long sessionInvalidateInMinutes) { // setter方法可以修改默认的120分钟
		this.sessionInvalidateInMinutes = sessionInvalidateInMinutes;
	}

	@Override
	public AuthUser login(String username, String password) {
		AuthUser authUser = this.authUserService.loadUser(username);
		if (authUser == null) {
			return null;
		}
		String salt = authUser.getSalt();
		if (!StringUtils.equals(authUser.getPassword(), PasswordEncoder.encrypt(salt, password))) {
			return null;
		}
		authUser.setLoginAt(new Date()); // 更新最后登录时间
		CurrentStaff staff = new CurrentStaff();
		BeanUtils.copyProperties(authUser, staff);
		this.authorize(staff, authUser);
		this.applySessionInvalidatePeriod(staff);
		String accessToken = encryption.aesEncrypt(getRawAccessToken(staff));
		authUser.setAccessToken(accessToken);
		return authUser;
	}

	/**
	 * 授权
	 * 
	 * @param currentStaff
	 */
	private void authorize(CurrentStaff currentStaff, AuthUser authUser) {
		currentStaff.setRoles(authUser.getRoles()); // 设置角色
		currentStaff.setPermissions(authUser.getPermissions()); // 设置权限
	}

	/**
	 * 设置session失效周期
	 * 
	 * @param currentStaff
	 */
	private void applySessionInvalidatePeriod(CurrentStaff currentStaff) {
		String key = RedisKeys.adminStaffToken(getStaffToken(currentStaff));
		redisOps.getValueOps().set(key, JsonUtils.toJson(currentStaff), sessionInvalidateInMinutes, TimeUnit.MINUTES); // 设置失效时间
	}

	private String getRawAccessToken(CurrentStaff currentStaff) {
		return StringUtils.join(currentStaff.getId(), ",", String.valueOf(currentStaff.getLoginAt().getTime()));// 增加随机性
	}

	private String getStaffToken(CurrentStaff currentStaff) {
		return getStaffToken(currentStaff.getId(), String.valueOf(currentStaff.getLoginAt().getTime()));// 增加随机性
	}

	private String getStaffToken(String staffId, String loginAt) {
		return StringUtils.join(staffId, ":", loginAt);
	}

	@Override
	public CurrentStaff getCurrentStaff(String accessToken) {
		try {
			String rawAccessToken = encryption.aesDecrypt(accessToken);
			String staffId = rawAccessToken.split(",")[0];
			String loginAt = rawAccessToken.split(",")[1];

			String staffJson = redisOps.getValueOps().get(RedisKeys.adminStaffToken(getStaffToken(staffId, loginAt)));
			CurrentStaff currentStaff = JsonUtils.fromJson(staffJson, CurrentStaff.class);
			return currentStaff;
		} catch (Exception e) {
			// 不关心具体的错误
			return null;
		}
	}

	@Override
	public void postAuthentication(CurrentStaff currentStaff) {
		resetSessionInvalidatePeriod(currentStaff);
	}

	/**
	 * 重置Session有效期
	 * 
	 * @param currentStaff
	 */
	private void resetSessionInvalidatePeriod(CurrentStaff currentStaff) {
		String key = RedisKeys.adminStaffToken(getStaffToken(currentStaff));
		redisOps.expire(key, sessionInvalidateInMinutes, TimeUnit.MINUTES); // 延长session失效时间
	}

	@Override
	public void postLogout(CurrentStaff currentStaff) {
		redisOps.deleteKey(RedisKeys.adminStaffToken(getStaffToken(currentStaff)));
	}

}
